﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Caching;

namespace Medianamik.Core.Caching
{
    public interface ICacheService<T>
    {
        T Get(string pCacheKey, DateTime pAbsoluteExpiration, CacheDependency cacheDependency,
            Func<T> pSourceDataDelegate);

        T Get(string pCacheKey, TimeSpan pSlidingExpiration, CacheDependency cacheDependency,
              Func<T> pSourceDataDelegate);

        T Get(string pCacheKey, CacheDependency cacheDependency, Func<T> pSourceDataDelegate);

        IList<T> GetCollection(string pCacheKey, CacheDependency dependency,
            Func<IList<T>> pSourceDataDelegate);

        IEnumerable<T> GetCollection(string pCacheKey, DateTime pAbsoluteExpiration, CacheDependency dependency,
                                            Func<IEnumerable<T>> pSourceDataDelegate);

        IEnumerable<T> GetCollection(string pCacheKey, TimeSpan pSlidingExpiration, CacheDependency dependency,
                                     Func<IEnumerable<T>> pSourceDataDelegate);
    }

    public class CacheService<T> : ICacheService<T>
    {
        private readonly TimeSpan _defaultSlidingExpiration = new TimeSpan(0, 0, 20);
        private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
        public string CacheKeyPrefix { get; set; }

        public CacheService()
        {
            CacheKeyPrefix = typeof(T).Name;
        }

        private readonly List<string> _cacheKeys = new List<string>();

        /// <summary>
        /// Gets an object from the cache or recreate it if it does not exist
        /// </summary>
        /// <param name="pCacheKey">The cache key</param>
        /// <param name="pAbsoluteExpiration"></param>
        /// <param name="pSlidingExpiration"></param>
        /// <param name="cacheDependency">Cache dependency to use</param>
        /// <param name="pSourceDataDelegate">The delegate to recreate the object</param>
        /// <returns>The requested object</returns>
        private U Get<U>(string pCacheKey, DateTime pAbsoluteExpiration, TimeSpan pSlidingExpiration, 
            CacheDependency cacheDependency, Func<U> pSourceDataDelegate)
        {
            _lock.EnterUpgradeableReadLock();
            try
            {
                var fullKey = GetFullCacheKey(pCacheKey);
                object obj = HttpRuntime.Cache.Get(fullKey);
                if (obj == null)
                {
                    obj = pSourceDataDelegate();
                    if (obj != null)
                    {
                        _lock.EnterWriteLock();
                        try
                        {
                            HttpRuntime.Cache.Insert(fullKey, obj, cacheDependency, pAbsoluteExpiration, pSlidingExpiration);
                            if (!_cacheKeys.Contains(fullKey))
                            {
                                _cacheKeys.Add(fullKey);
                            }
                        }
                        finally
                        {
                            _lock.ExitWriteLock();
                        }
                    }
                }

                return (U)obj;
            }
            catch (InvalidCastException e)
            {
                throw new InvalidCastException(string.Concat("Unable to cast CacheItem '", pCacheKey, "'."), e);
            }
            finally
            {
                _lock.ExitUpgradeableReadLock();
            }
        }

        public T Get(string pCacheKey, DateTime pAbsoluteExpiration, CacheDependency cacheDependency, Func<T> pSourceDataDelegate)
        {
            return Get(pCacheKey, pAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, cacheDependency, pSourceDataDelegate);
        }

        public T Get(string pCacheKey, TimeSpan pSlidingExpiration, CacheDependency cacheDependency, Func<T> pSourceDataDelegate)
        {
            return Get(pCacheKey, System.Web.Caching.Cache.NoAbsoluteExpiration, pSlidingExpiration, cacheDependency, pSourceDataDelegate);
        }

        public T Get(string pCacheKey, CacheDependency cacheDependency, Func<T> pSourceDataDelegate)
        {
            return Get(pCacheKey, System.Web.Caching.Cache.NoAbsoluteExpiration, _defaultSlidingExpiration, cacheDependency, pSourceDataDelegate);
        }

        public IEnumerable<T> GetCollection(string pCacheKey, DateTime pAbsoluteExpiration, CacheDependency dependency, Func<IEnumerable<T>> pSourceDataDelegate)
        {
            return Get(pCacheKey, pAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, dependency, pSourceDataDelegate);
        }

        public IEnumerable<T> GetCollection(string pCacheKey, TimeSpan pSlidingExpiration, CacheDependency dependency, Func<IEnumerable<T>> pSourceDataDelegate)
        {
            return Get(pCacheKey, System.Web.Caching.Cache.NoAbsoluteExpiration, pSlidingExpiration, dependency, pSourceDataDelegate);
        }

        public IList<T> GetCollection(string pCacheKey, CacheDependency dependency, Func<IList<T>> pSourceDataDelegate)
        {
            return Get(pCacheKey, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, dependency, pSourceDataDelegate);
        }

        public void Clear()
        {
            _lock.EnterWriteLock();
            try
            {
                _cacheKeys.ForEach(k => HttpRuntime.Cache.Remove(k));
                _cacheKeys.Clear();
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }

        public object Remove(string pCacheKey)
        {
            var fullKey = GetFullCacheKey(pCacheKey);
            _lock.EnterReadLock();
            try
            {

                if (_cacheKeys.Contains(fullKey))
                {
                    return HttpRuntime.Cache.Remove(fullKey);
                }
                return null;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        private string GetFullCacheKey(string pPartialKey)
        {
            return CacheKeyPrefix + pPartialKey;
        }
    }
}